﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;

using TJDevHouse.Starcraft.Game;

namespace TJDevHouse.Starcraft.MapAnalyser.Analysers
{
	public class WalkabilityAnalyser : Analyser
	{
		protected WalkabilityZoneAnalysisResult _result;
		public WalkabilityZoneAnalysisResult Result {
			get
			{
				if (_result == null)
				{
					DoAnalysis();
				}
				return _result;
			}
			private set
			{
				_result = value;
			}
		}



		public WalkabilityAnalyser(Map map, MapAnalysisController analysisController) : base(map, analysisController) { }

		private int _lastGeneratedZoneID = 0;

		public override void DoAnalysis()
		{
			Result = new WalkabilityZoneAnalysisResult(Map);

			/*
			 * run through map tiles and check each one that has not been checked...
			 * 
			 * if it is not walkable, add it to zone 0 and go to next tile
			 * 
			 * if is walkable then check if tile above walkable. if the tile above is walkable, then
			 * this zone already defined, set this tile to that zone id and change the zone id of all
			 * the previous tiles of the current zone id
			 * 
			 * 
			 */

			WalkTile prevTile = Map.GetWalkTile(0, 0);

			int currentZone;

			for (int y = 0; y < Map.MapWalkableHeight; y++)
			{
				currentZone = GenerateNextZoneID();
				for (int x = 0; x < Map.MapWalkableWidth; x++)
				{
					WalkTile thisTile = Map.GetWalkTile(x, y);

					if (!thisTile.IsWalkable)
					{
						Result.AddTileToZone(0, thisTile);
					}
					else
					{
						if (!prevTile.IsWalkable)
						{
							currentZone = GenerateNextZoneID();
						}

						Result.AddTileToZone(currentZone, thisTile);

						//check above
						if (y - 1 >= 0 && Map.GetWalkTile(x, y - 1).IsWalkable)
						{
							currentZone = HandleZoneMerge(currentZone, thisTile);
						}
					}
					prevTile = thisTile;
				}
			}

			Analysed = true;
		}

		private int GenerateNextZoneID()
		{
			_lastGeneratedZoneID++;
			return _lastGeneratedZoneID;
		}

		

		/// <summary>
		/// Joins a zone with another zone and returns the zone id to use for defining
		/// that zone from now on.
		/// 
		/// Checks to see which zone has the fewest tiles in it, then changes the zone ids of
		/// the smallest zone to the other.
		/// </summary>
		/// <param name="currentZone"></param>
		/// <param name="tile"></param>
		/// <returns></returns>
		private int HandleZoneMerge(int currentZone, WalkTile tile)
		{
			
			int aboveZoneID, minZoneID, maxZoneID;

			aboveZoneID = Result.ZonesByTiles[tile.Y - 1, tile.X];
			if (aboveZoneID == currentZone)
			{
				return currentZone;
			}

			//Console.WriteLine("Merging zone " + currentZone + " with " + aboveZoneID);


			if (Result.TilesByZone[currentZone].Count > Result.TilesByZone[aboveZoneID].Count)
			{
				minZoneID = aboveZoneID;
				maxZoneID = currentZone;
			}
			else
			{
				maxZoneID = aboveZoneID;
				minZoneID = currentZone;
			}

			//Console.WriteLine("zone " + maxZoneID + " has more tiles than zone " + minZoneID);

			foreach (WalkTile toChange in Result.TilesByZone[minZoneID].ToList())
			{
				Result.AddTileToZone(maxZoneID, toChange);
			}

			//remove from the results
			Result.TilesByZone.Remove(minZoneID);

			return maxZoneID;
		}




		public override void SaveAnalysisResult()
		{
			if (Result == null)
			{
				DoAnalysis();
			}

			//grab the appropriate writer
			StreamWriter writer = AIModuleConnector.ImportExport.MapDataStorageController.GetMapAnalysisDataWriter(Map, "walkabilityAnalysis");

			//simplify the zone ids
			Dictionary<int, int> zoneIDMaps = new Dictionary<int, int>();
			int lastID = 0;
			foreach (int origZoneID in Result.TilesByZone.Keys)
			{
				//zone 0 (not walkable) is always zone 0
				if (origZoneID == 0)
				{
					zoneIDMaps.Add(0, 0);
				}
				else
				{
					zoneIDMaps.Add(origZoneID, lastID);
					lastID++;
				}
			}

			//save each walk tile's zone id
			//separate with "." symbol
			string line = "";
			for (int y = 0; y < Map.MapWalkableHeight; y++)
			{
				for (int x = 0; x < Map.MapWalkableWidth; x++)
				{
					if (x != 0)
					{
						line += ".";
					}
					int thisZone = Result.ZonesByTiles[y, x];
					line += zoneIDMaps[thisZone];
				}
				writer.WriteLine(line);
				line = "";
			}
			writer.Flush();
			writer.Close();
		}

		public override string GetAnalyserRefName()
		{
			return "walkabilityAnalysis";
		}

		public override void LoadAnalysisResult()
		{
			StreamReader reader = AIModuleConnector.ImportExport.MapDataStorageController.GetMapAnalysisDataReader(Map, AnalyserRefName);

			Result = new WalkabilityZoneAnalysisResult(Map);

			//go through with a for loop to ensure exception thrown on incomplete data
			for (int y = 0; y < Map.MapWalkableHeight; y++)
			{
				string[] tiles = reader.ReadLine().Split('.');
				for (int x = 0; x < Map.MapWalkableWidth; x++)
				{
					Result.AddTileToZone(int.Parse(tiles[x]), Map.GetWalkTile(x, y));
				}
			}

			reader.Close();
			Analysed = true;
		}

		/// <summary>
		/// Checks whether the supplied walk tiles are in the same walkability
		/// zone.
		/// </summary>
		/// <param name="t1"></param>
		/// <param name="t2"></param>
		/// <returns></returns>
		public bool IsTileWalkableFrom(WalkTile t1, WalkTile t2)
		{
			return GetWalkabilityZoneForTile(t1) == GetWalkabilityZoneForTile(t2);
		}

		/// <summary>
		/// Gets the walkability zone id for the supplied walk tile
		/// </summary>
		/// <param name="tile"></param>
		/// <returns></returns>
		public int GetWalkabilityZoneForTile(WalkTile tile)
		{
			return Result.ZonesByTiles[tile.Y, tile.X];
		}
	}
}
